/* browser_window.js

  Copyright (C) 2006 by Lukas Loehrer
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

  Author: loehrerl [at] gmx [dot] net
*/

/* Commentary:
This file implements a class that encapsulates a browser window (or maybe tab)
Instances of this class are created int eh browser_service service.
*/
const Ci = Components.interfaces;
if (typeof(module) == "object") {
	const logger = module.require("package", "lib/filelogger").logger;

	const util = module.require("package", "mozremote/util");

	try {
		const IRBuilder = module.require("package", "ir_builder_acc");
	} catch (e) {
		logger.error("Failed to import ir_builder: " + e, e);
	}

	try {
		const Inspector = module.require("class", "mozremote/services/inspector/inspector");
	} catch (e) {
		logger.error("Failed to import inspector: " + e, e);
	}

	const dom_util = module.require("package", "dom_util");
	const acc_util = module.require("package", "accessibility_util");
}

/*
  Constructor for browser windows.
  Either wrap an existing browser or create one in a new tab.
   Thus, The "browser argument is optional."
*/

function constructor(browsingService, browser) {
	this._browsingService = browsingService;
	this._browser = browser;
	
	// For building the intermediate representation
	this._IRBuilder = new IRBuilder.Builder(this._browsingService);
	
	this._addWindowListeners();
}

function close() {
	this._browsingService.closeBrowserWindow(this);
}

function closeInternal() {
	try {
		this.closing = true;
		var win = this._getContentWindow();
		this._cleanup();
		if (win && !win.closed) {
			win.close();
		}
	} catch (e) {
		logger.error("browser_window.closeInternal: " + e, e);
	}
}

// this function is idempotent
function _cleanup() {
		// Remove potential references to firefox objects
	if (this._progressListener != null) {
		this._getBrowser().webProgress.removeProgressListener(this._progressListener);
		this._progressListener = null;
	}
	this._browser = null;
	this._ir = null;
}

// {{{Remote interface (called via object_refs.callMethod

// This version sends the js-return message only when the page has loaded completely.
function loadURLAndWait(url, referrer) {
	this.loadURL(url, referrer);
	var _this = this;
	var onLoadCallback = function (event) { return _this._getContentDocument().title; };
	return this._browsingService._remote.returnLaterOnEvent(this._getContentWindow(), "load", onLoadCallback, true);
}

function loadURL(url, referrer) {
this._getBrowser().loadURI(url);
}

/* Returns some summary information about the currently loaded web page in this window." */
function getDocumentInfo() {
	var doc = this._getContentDocument();
	return {
		title: doc.title,
				   url: doc.URL,
				   referrer: doc.referrer,
				   charset: doc.characterSet,
				   forms: doc.forms.length,
				   anchors: doc.anchors.length,
				   links: doc.links.length,
				   lastModified: doc.lastModified.toString()
				   };
}

/*
 * This method is supposed to return a representation of the DOM that
 * will allow Firemacs to display the currently loaded web page and
 * refer to interesting elements like links or form fields in later
 * interactions.
*/
function getIR(aNode) {
	aNode = aNode || this._getContentDocument();
	var startTime = Date.now();
	var ir = this._IRBuilder.visitDOMNode(aNode);
	logger.profile(startTime, "Generating IR");
	return ir;
}

// {{{Query information about browser window 

function getSessionHistory() {
	var sh = this._getBrowser().sessionHistory;

	var shEnum = sh.SHistoryEnumerator;
	var result = [];
	while (shEnum.hasMoreElements()) {
		var sessionHistoryEntry = shEnum.getNext();
		var SHEntry = sessionHistoryEntry.QueryInterface(Ci.nsISHEntry);
		result.push([SHEntry.title, SHEntry.URI.spec]);
	}
	return {
		index: sh.index,
				   history: result
				   };
}

// }}}

// {{{Web navigation 

function followLink(aNode) {
	/*
	var anchorNode = dom_util.getFirstAncestorByTagName(aNode, "A");
	if (anchorNode) {
		var acc = acc_util.getAccessibleFor(anchorNode);
		if (acc) {
			acc.doAction(0);
		}
		return true;
	} else {
		return false;
	}
	*/

	dom_util.simulateClick(aNode);
}

function goBackward() {
	this._getBrowser().goBack();
}

function goForward() {
	this._getBrowser().goForward();
}

function gotoSessionHistoryIndex(index) {
	var b = this._getBrowser();
	if (index >= 0 && index < b.sessionHistory.count) {
		b.gotoIndex(index);
		return b.sessionHistory.index;
	} else {
		return null;
	}
}

function stopLoading() {
	this._getBrowser().stop();
}

function reloadPage() {
	this._getBrowser().reload();
}

// }}}

// {{{Form actions

function getComboboxOptions(aCombobox ) {
	if (!(aCombobox instanceof Ci.nsIDOMHTMLSelectElement)) {
		return null;
	}

	var options = [];
	for (var i = 0; i < aCombobox.options.length; ++i) {
		options.push(aCombobox.options[i].text);
	}
	return {
		selected: aCombobox.selectedIndex,
					  options: options
					  };
}

function selectComboboxOption(aCombobox, aIndex) {
	if (!(aCombobox instanceof Ci.nsIDOMHTMLSelectElement)) {
		return null;
	}

	aCombobox.selectedIndex = aIndex;
	dom_util.simulateChange(aCombobox);
	return aCombobox.value;
}

function submitForm(aNode) {
	var form = aNode.form;
	if (form) {
		// The following is needed because form.submit() does not trigger the onsubmit event handler
		if (!form.onsubmit || form.onsubmit()) {
			form.submit();
		}
	}
}

function pressPushButton(aNode) {

	
	return dom_util.simulateClick(aNode);
}

function toggleCheckbox(aNode) {
	if (!(aNode instanceof Ci.nsIDOMHTMLInputElement && aNode.type == "checkbox")) {
		return null;
	}
	dom_util.simulateClick(aNode);
	return {
		checked: aNode.checked
					 };
}

function selectRadiobutton(aNode) {
	if (!(aNode instanceof Ci.nsIDOMHTMLInputElement && aNode.type == "radio")) {
		return null;
	}
	dom_util.simulateClick(aNode);
	return {
		checked: aNode.checked
					 };
}

function setTextEntryValue(aNode, aText) {
	aNode.focus();
	aNode.value = aText;
	aNode.blur();
	dom_util.simulateChange(aNode);
	return aNode.value;
}

// }}}

// {{{Table commands 

function getTableCellInfo(aCell) {
	var accCell = acc_util.getAccessibleFor(aCell);
	var result = acc_util.getTableCellInfo(accCell);
	return result;
}

function _moveInTable(aCell, movementFunction) {
	var result = null;
	try {
		var accCell = acc_util.getAccessibleFor(aCell);
		var targetCell= movementFunction(accCell);
		if (targetCell) {
			logger.debug("Got target cell");
			result = acc_util.getTableCellInfo(targetCell);
			if (result) {
				result.cell = acc_util.getDOMNodeFor(targetCell);
			}
		} else {
			logger.debug("No target cell");
		}
	} catch (e) {
		logger.error("browser_window._moveInTable: " + e, e);
	}
	return result;
}

function getTableCellInfoAbove(aCell) {
	return this._moveInTable(aCell, acc_util.getTableCellAbove);
}

function getTableCellInfoBelow(aCell) {
	return this._moveInTable(aCell, acc_util.getTableCellBelow);
}

function getTableCellInfoLeft(aCell) {
	return this._moveInTable(aCell, acc_util.getTableCellLeft);
}

function getTableCellInfoRight(aCell) {
	return this._moveInTable(aCell, acc_util.getTableCellRight);
}

// }}}


// Returns a DOM inspector object for this browser window. 
function createInspector() {
	return this._browsingService.bless(new Inspector(this._getContentWindow()));
}

// }}}

// {{{Internal methods 

// {{{Page Loading 

function _onLoadStart(aRequest, aTopWindow) {
	aTopWindow = util.unwrap(aTopWindow);
	var isTopLoad = (aTopWindow ==aTopWindow.top);
	var url = aRequest.QueryInterface(Ci.nsIChannel).URI.spec;
	logger.debug("Start request for " + url);

	var event = {
		window: this,
		url: url,
		"top-load": isTopLoad
	};
	this._browsingService._fireEvent("load-start", event);
}

function _onInternalJump(aLocationURL, aDOMWindow)  {
	logger.debug("browser_window: Jump to anchor " + aLocationURL.spec);
	var event = {
		window: this,
		document: aDOMWindow.document,
		"fm-url": aDOMWindow.document.location.href,
		"session-history-id": this._getCurrentSHistoryEntryId()
	};

	var targetNode = dom_util.possibleTargetNodeFromURI(aLocationURL, aDOMWindow.document.body);
	if (targetNode) {
		event.node = targetNode;
	}

	this._browsingService._fireEvent("internal-jump", event);
}

function _onLoadComplete(aRequest, aDOMWindow, aIsInSubFrame, aIsRestore, aIsReload, aIsRefresh) {
	/*
	  if (aIsRestore) getCursorPosFromHistory();
	  else  if (aIsRefresh || aIsInSubframe) tryKeepCursorPos();
	  else { setCursorAccordingToHash() }
	*/

	var win = util.unwrap(aDOMWindow);
	var url = aRequest.QueryInterface(Ci.nsIChannel).URI.spec;
	var originalUrl = aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec;
	var targetNode = dom_util.possibleTargetNodeFromURI(aRequest.QueryInterface(Ci.nsIChannel).URI, win.document.body);

	logger.debug("browser_window: Load complete:" +
				 " isInSubFrame=" + aIsInSubFrame +
				 " isRestore=" + aIsRestore +
				 " isReload=" + aIsReload +
				 " isRefresh=" + aIsRefresh + " " + 
				 url + " " + originalUrl);
	
	if (aIsInSubFrame) {
		var frameElement = dom_util.getFrameElementForSubWindow(win);
		if (frameElement == null) {
			logger.error("browser_window._onLoadComplete: Cannot find frame element.");
		} else {
			var event = {
				window: this,
				frame: frameElement
			};
			this._browsingService._fireEvent("frame-load-complete", event);
		}
	} else {
		var event = {
			window: this,
			title: win.document.title,
			"is-restore": aIsRestore,
			"session-history-id": this._getCurrentSHistoryEntryId(),
					};
		if (targetNode) {
			event.node = this._browsingService.bless(targetNode);
		}
		this._browsingService._fireEvent("load-complete", event);
	}
}

function _onSubFrameLoadComplete(aRequest, aTopWindow) {
	aTopWindow = util.unwrap(aTopWindow);

var url = aRequest.QueryInterface(nsIChannel).URI.spec;
var originalUrl = aRequest.QueryInterface(nsIChannel).originalURI.spec;
	logger.debug("browser_window: SubFrameLoad complete: " + url + " " + originalUrl);


}

function _onLoadStatusChange(aMessage) {
	logger.debug("browser_window: Load status change: " + aMessage);
}

function _listSubFrames(aWindow) {
	var frames = [];
	for  (var i=0; i<aWindow.frames.length; ++i) {
		var frame = aWindow.frames[i];
		frames.push(frame);
		frames.push.apply(frames, this._listSubFrames(frame));
	}
	return frames;
}

function onSubtreeModified(aNode) {
	var event = {
		window: this,
		node: this._browsingService.bless(aNode)
	};
	this._browsingService._fireEvent("subtree-modified", event);
	
}

function onStateChange(aNode) {
	// Currently, we are only interested in state changes for form
	// controls like radiobuttons and checkboxes
	if (aNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
		if (aNode.tagName.toLowerCase() == "input") {
			if (aNode.type == "radio" || aNode.type == "checkbox") {
				var event = {
					window: this,
					node: this._browsingService.bless(aNode),
					checked: aNode.checked
				};
				this._browsingService._fireEvent("state-change", event);
			}
		}
	}
}

// }}}






function _refreshIR() {
	var doc = this._getContentDocument();

}

function _getBrowser() {
	if (!this._browser) {
		throw new Error("browser_window: Object not associated with an actual browser. Already closed?.");
	}
return this._browser;
}

function _getContentWindow() {
	return this._getBrowser().contentWindow;
}

function _getContentDocument() {
	return this._getBrowser().contentDocument;
}

function _getCurrentSHistoryEntryId() {
	var sh = this._getBrowser().webNavigation.sessionHistory;
	var entry = sh.getEntryAtIndex(sh.index, false).QueryInterface(Ci.nsISHEntry);
	
	return entry.ID;
}

/*
  Returns a list of the documents in all the frames of this window
*/
function _listDocuments() {
}

/* Add event listeners on the content window
 */
function _addWindowListeners() {
	this._progressListener = new dom_util.SimpleWebProgressListener(this);
	this._getBrowser().webProgress.addProgressListener(this._progressListener, Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_LOCATION);
}

// }}}

// {{{ Emacs local variables

// local variables:
// folded-file: t
// end:

// }}}
